﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using CobraCms.Core.Content.Attributes;
using CobraCms.Core.Utilities;

namespace CobraCms.Core.Content
{
    public class ReflectedDefinitionIndex
    {
        private readonly Dictionary<string, ContentNodeDefinition> _nodeDefinitionIdLookup;
        private readonly Dictionary<Type, ContentNodeDefinition> _nodeDefinitionTypeLookup;

        public ReflectedDefinitionIndex()
        {
            _nodeDefinitionIdLookup = new Dictionary<string, ContentNodeDefinition>();
            _nodeDefinitionTypeLookup = new Dictionary<Type, ContentNodeDefinition>();
            
        }
        public void BuildIndex()
        {            
            var contentNodeTypes = ReflectionUtils.GetTypesFromExecutionDirectoryWithAttribute(typeof(ContentNodeDefinitionAttribute));
            foreach (var contentNodeType in contentNodeTypes)
            {
                var nodeDefinition = new ContentNodeDefinition();
                nodeDefinition.DefinitionType = contentNodeType;
                nodeDefinition.DefinitionSettings = ReflectionUtils.GetAttributeInstanceFromType<ContentNodeDefinitionAttribute>(contentNodeType);

                #region Validation

                if (nodeDefinition.DefinitionSettings == null)                
                {
                    throw new InvalidDataException(
                        String.Format("Invalid ContentNodeDefinition in type {0}. Attribute of type {1} not found.",
                        contentNodeType.FullName, typeof(ContentNodeDefinitionAttribute).Name));
                }

                ContentNodeDefinition existingDefinition;
                if (_nodeDefinitionIdLookup.TryGetValue(nodeDefinition.DefinitionSettings.DefinitionId, out existingDefinition))                
                {
                    throw new InvalidDataException(
                        String.Format("Invalid ContentNodeDefinition in type {0}. DefinitionId {1} has already been used by type {2}.",
                        contentNodeType.FullName, nodeDefinition.DefinitionSettings.DefinitionId, existingDefinition.DefinitionType.FullName));
                }
                if (_nodeDefinitionTypeLookup.TryGetValue(nodeDefinition.DefinitionType, out existingDefinition))
                {
                    throw new InvalidDataException(
                        String.Format("Invalid ContentNodeDefinition in type {0}. Type already registered.",
                        contentNodeType.FullName));
                }

                #endregion

                _nodeDefinitionIdLookup.Add(nodeDefinition.DefinitionSettings.DefinitionId, nodeDefinition);
                _nodeDefinitionTypeLookup.Add(nodeDefinition.DefinitionType, nodeDefinition);
            }
        }

        public ContentNodeDefinition GetContentNodeDefinition(string definitionId)
        {
            ContentNodeDefinition definition;
            _nodeDefinitionIdLookup.TryGetValue(definitionId, out definition);
            return definition;
        }

        public List<ContentNodeDefinition> ListRootContentNodeDefinitions()
        {
            return _nodeDefinitionIdLookup.Values.Where(c => c.DefinitionSettings.RootNode).ToList();
        }

        public List<ContentNodeDefinition> ListAllowedChildContentNodeDefinitions(string parentContentNodeDefinitionId)
        {
            var definitions = new List<ContentNodeDefinition>();
            var definition = GetContentNodeDefinition(parentContentNodeDefinitionId);
            if (definition == null)
                return definitions;

            foreach (var childType in definition.DefinitionSettings.AllowedChildren)
            {
                ContentNodeDefinition childDefinition;
                if (_nodeDefinitionTypeLookup.TryGetValue(childType, out childDefinition))
                {
                    definitions.Add(childDefinition);
                }
            }
            return definitions;
        }
    }
}
